package com.partsoft.loader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.PropertyConfigurator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.partsoft.loader.context.ContextServiceAdapter;
import com.partsoft.loader.log.Log;
import com.partsoft.utils.StringUtils;

public class ContextLauncher implements ContextServiceAdapter {

	public static final String RootDirParameterName = "app.root.dir";

	public static final String ConfigDirParameterName = "app.config.dir";

	public static final String ConfigFileParameterName = "app.config.file";

	public static final String DataDirParameterName = "app.data.dir";

	public static final String StartPackageParameterName = "app.start.pkg";

	public static final String AppTitleParameterName = "app.title";

	public static final String WebDirParameterName = "app.web.dir";

	private boolean hookShutdown = true;

	private ShutdownHookThread hookThread = null;

	private static ApplicationContext launcherContext = null;

	public void setHookShutdown(boolean hookShutdown) {
		this.hookShutdown = hookShutdown;
	}

	public boolean isHookShutdown() {
		return hookShutdown;
	}

	public static ApplicationContext getApplicationContext() {
		return launcherContext;
	}

	// 初始化应用参数
	protected void initAppParameters() throws Exception {
		String startDir = new File("").getAbsolutePath();
		String currentDir = new File("..").getAbsolutePath();
		String tempDir = System.getProperty(RootDirParameterName);
		String rootDir = tempDir;
		if (!StringUtils.hasText(rootDir)) {
			rootDir = currentDir;
			System.setProperty(RootDirParameterName, rootDir);
		}

		String configDir = System.getProperty(ConfigDirParameterName);
		if (!StringUtils.hasText(configDir)) {
			configDir = rootDir + "/conf";
			System.setProperty(ConfigDirParameterName, configDir);
		} else if (configDir.startsWith(".")) {
			configDir = startDir + "/" + configDir;
			System.setProperty(ConfigDirParameterName, configDir);
		}

		String configFName = System.getProperty(ConfigFileParameterName);
		if (!StringUtils.hasText(configFName)) {
			configFName = configDir + "/app.conf";
		}

		if (!new File(configFName).exists()) {
			throw new FileNotFoundException(String.format("应用配置文件(%s)不存在", configFName));
		}

		String dataDir = System.getProperty(DataDirParameterName);
		if (!StringUtils.hasText(dataDir)) {
			dataDir = rootDir + "/data";
			System.setProperty(DataDirParameterName, dataDir);
		} else if (dataDir.startsWith(".")) {
			dataDir = startDir + "/" + dataDir;
			System.setProperty(DataDirParameterName, dataDir);
		}

		tempDir = System.getProperty(WebDirParameterName);
		if (!StringUtils.hasText(tempDir)) {
			tempDir = rootDir + "/web";
			System.setProperty(WebDirParameterName, tempDir);
		}

		String logConfigFile = configDir + "/log4j.properties";
		boolean logConfiged = false;
		if (new File(logConfigFile).exists()) {
			try {
				PropertyConfigurator.configure(logConfigFile);
				logConfiged = true;
			} catch (Throwable e) {
			}
		}

		if (!logConfiged) {
			System.out.println(String.format("无法加载日志配置文件(%s)，将使用默认日志配置", logConfigFile));
			try {
				PropertyConfigurator.configure(this.getClass().getResourceAsStream("default.log4j.properties"));
				logConfiged = true;
			} catch (Throwable e) {
			}
		}

		Properties configProperties = new Properties();
		try {
			configProperties.load(new InputStreamReader(new FileInputStream(configFName), "utf-8"));
		} catch (IOException e) {
			throw new IOException(String.format("加载应用配置文件(%s)时出错: %s", configFName, e.getMessage()), e);
		}

		String appStartType = System.getProperty(StartPackageParameterName);
		if (!StringUtils.hasText(appStartType)) {
			if (configProperties.containsKey(StartPackageParameterName)) {
				appStartType = configProperties.getProperty(StartPackageParameterName);
			} else {
				Log.warn(String.format("应用配置文件未指定启动类型(%s)，将使用默认启动类型。", StartPackageParameterName));
				appStartType = "userprovider";
			}
			System.setProperty(StartPackageParameterName, appStartType);
		}

		String appTitle = System.getProperty(AppTitleParameterName);
		if (!StringUtils.hasText(appTitle)) {
			if (configProperties.containsKey(AppTitleParameterName)) {
				appTitle = configProperties.getProperty(AppTitleParameterName);
			} else {
				appTitle = "未命名应用";
			}
			System.setProperty(AppTitleParameterName, appTitle);
		}

		if (Log.isDebugEnabled()) {
			Log.debug("=================系统参数初始化设置完成=================");
			Log.debug(" 系统主目录:\t\t" + rootDir);
			Log.debug(" 系统配置目录:\t" + configDir);
			Log.debug(" 应用配置文件:\t" + configFName);
			Log.debug(" 日志配置文件:\t" + logConfigFile);
			Log.debug(" 启动类型:\t\t" + appTitle);
			Log.debug("================================================");
		}

	}

	protected String resolveAppStartPackageName() {
		String resultStartType = "";
		String tempStartType = System.getProperty(StartPackageParameterName);
		if (!StringUtils.hasText(tempStartType)) {
			String tempDir = System.getProperty(ConfigDirParameterName);
			Properties r = new Properties();
			try {
				r.load(new InputStreamReader(new FileInputStream(new File(tempDir + "/app.conf")), "utf-8"));
				if (r.containsKey(StartPackageParameterName)) {
					resultStartType = r.getProperty(StartPackageParameterName);
				}
			} catch (IOException e) {

			}
		} else {
			resultStartType = tempStartType;
		}
		return resultStartType;
	}

	protected String resolveAppTitle() {
		String result = "未命名应用";
		String appTitle = System.getProperty(AppTitleParameterName);
		if (!StringUtils.hasText(appTitle)) {
			String tempDir = System.getProperty(ConfigDirParameterName);
			Properties r = new Properties();
			try {
				r.load(new InputStreamReader(new FileInputStream(new File(tempDir + "/app.conf")), "utf-8"));
				if (r.containsKey(AppTitleParameterName)) {
					result = r.getProperty(AppTitleParameterName);
				}
			} catch (IOException e) {
			}
		} else {
			result = appTitle;
		}
		return result;
	}

	protected void startServices() {
		Map<String, ContextServiceAdapter> services = null;
		try {
			services = getApplicationContext().getBeansOfType(ContextServiceAdapter.class);
		} catch (Throwable e) {
			Log.warn("未发现上下文服务适配器接口实现");
		}

		if (services != null) {
			try {
				for (ContextServiceAdapter serviceAdapter : services.values()) {
					serviceAdapter.start();
				}
			} catch (Throwable e) {
				try {
					for (ContextServiceAdapter serviceAdapter : services.values()) {
						serviceAdapter.stop();
					}
				} catch (Throwable ex) {
					Log.ignore(ex);
				}
				throw new IllegalStateException(e.getMessage(), e);
			}
		}
	}

	protected void stopServices() {
		try {
			Map<String, ContextServiceAdapter> services = getApplicationContext().getBeansOfType(ContextServiceAdapter.class);
			for (ContextServiceAdapter serviceAdapter : services.values()) {
				serviceAdapter.stop();
			}
		} catch (Throwable e) {
			Log.warn("未发现上下文服务适配器接口实现");
		}
	}

	public void start(String[] args) {
		String appTitle = null;
		try {
			initAppParameters();
			String startPackageName = resolveAppStartPackageName();
			appTitle = resolveAppTitle();

			ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext();
			startPackageName = startPackageName.replace(".", "/");
			appContext.setConfigLocation(String.format("classpath:%s/app.xml", startPackageName));
			launcherContext = appContext;
			appContext.refresh();
			startServices();

			hookThread = new ShutdownHookThread(this);
			new Thread(new Runnable() {
				public void run() {
					while (!hookThread.isShutdowning()) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
						}
					}
				}
			}).start();
			System.out.println(String.format("%s成功启动！", appTitle));
		} catch (Throwable e) {
			System.out.println(String.format("启动%s时出错！", appTitle != null ? appTitle : "应用"));
			if (Log.isDebugEnabled()) {
				Log.debug(e.getMessage(), e);
			}
			if (e instanceof RuntimeException) {
				throw (RuntimeException)e;
			} else {
				throw new IllegalStateException(e.getMessage(), e);
			}
		}
	}

	void stopContext() {
		if (launcherContext != null) {
			stopServices();
			if (launcherContext instanceof ConfigurableApplicationContext) {
				((ConfigurableApplicationContext) launcherContext).close();
			}
			launcherContext = null;
		}
	}

	public void start() throws Exception {
		start(new String[0]);
	}

	public void stop() {
		if (isHookShutdown() && this.hookThread != null && this.hookThread.isShutdowning()) {
			this.hookThread.setShutdowning(true);
		} else if (!isHookShutdown()) {
			stopContext();
		}
	}

	private static class ShutdownHookThread extends Thread {

		private final ContextLauncher contextLauncher;

		private boolean shutdowning = false;

		ShutdownHookThread(ContextLauncher contextLauncher) {
			this.contextLauncher = contextLauncher;
			createShutdownHook();
		}

		void setShutdowning(boolean shutdowning) {
			this.shutdowning = shutdowning;
		}

		private void createShutdownHook() {
			try {
				Method shutdownHook = java.lang.Runtime.class.getMethod("addShutdownHook", new Class[] { java.lang.Thread.class });
				shutdownHook.invoke(Runtime.getRuntime(), new Object[] { this });
			} catch (Exception e) {
				if (Log.isDebugEnabled()) {
					Log.debug("JAVA虚拟机无法添加关闭钩子", e);
				}
			}
		}

		public boolean isShutdowning() {
			return shutdowning;
		}

		public void run() {
			setName("Shutdown");
			Log.info("关闭中...");
			try {
				contextLauncher.stopContext();
			} catch (Throwable e) {
				Log.ignore(e);
			}
			shutdowning = true;
		}

	}

	public static void main(String[] args) {
		try {
			new ContextLauncher().start(args);
		} catch (Throwable e) {
			System.out.println(String.format("启动时候出现错误： %s", e.getMessage()));
			Log.error(e.getMessage(), e);
		}
	}

}
